import crypto from 'node:crypto'
import http, { AgentOptions } from 'node:http'
import https from 'node:https'
import path from 'node:path'
import querystring from 'node:querystring'

import type { S3ClientConfig } from '@aws-sdk/client-s3'
import { DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3'
import logger from '@core/picgo/logger'
import { NodeHttpHandler } from '@smithy/node-http-handler'
import axios from 'axios'
import type { ISftpPlistConfig } from 'piclist'

import type { IObj, IStringKeyMap } from '#/types/types'
import { getAgent } from '~/manage/utils/common'
import SSHClient from '~/utils/sshClient'

interface DogecloudTokenFull {
  Credentials: {
    accessKeyId: string
    secretAccessKey: string
    sessionToken: string
  }
  ExpiredAt: number
  Buckets: {
    name: string
    s3Bucket: string
    s3Endpoint: string
  }[]
}

const dogeRegionMap: IStringKeyMap = {
  'ap-shanghai': '0',
  'ap-beijing': '1',
  'ap-guangzhou': '2',
  'ap-chengdu': '3'
}

async function dogecloudApi(
  apiPath: string,
  data = {},
  jsonMode: boolean = false,
  accessKey: string,
  secretKey: string
) {
  const body = jsonMode ? JSON.stringify(data) : querystring.encode(data)
  const sign = crypto
    .createHmac('sha1', secretKey)
    .update(Buffer.from(apiPath + '\n' + body, 'utf8'))
    .digest('hex')
  const authorization = `TOKEN ${accessKey}:${sign}`
  try {
    const res = await axios.request({
      url: `https://api.dogecloud.com${apiPath}`,
      method: 'POST',
      data: body,
      responseType: 'json',
      headers: {
        'Content-Type': jsonMode ? 'application/json' : 'application/x-www-form-urlencoded',
        Authorization: authorization
      }
    })
    if (res.data.code !== 200) {
      throw new Error('API Error')
    }
    return res.data.data
  } catch (err: any) {
    throw new Error('API Error')
  }
}

async function getDogeToken(accessKey: string, secretKey: string): Promise<IObj | DogecloudTokenFull> {
  try {
    const data = await dogecloudApi(
      '/auth/tmp_token.json',
      {
        channel: 'OSS_FULL',
        scopes: ['*']
      },
      true,
      accessKey,
      secretKey
    )
    return data
  } catch (err: any) {
    logger.error(err)
    return {}
  }
}

export async function removeFileFromS3InMain(configMap: IStringKeyMap, dogeMode: boolean = false) {
  const {
    url: rawUrl,
    type,
    config: {
      accessKeyID,
      secretAccessKey,
      bucketName,
      endpoint,
      pathStyleAccess,
      rejectUnauthorized,
      proxy,
      urlPrefix
    }
  } = configMap
  let {
    imgUrl,
    config: { region }
  } = configMap
  if (type === 'aws-s3' || type === 'aws-s3-plist') {
    imgUrl = rawUrl || imgUrl || ''
  }
  let fileKey
  if (urlPrefix && imgUrl.startsWith(urlPrefix)) {
    const urlPrefixObj = new URL(urlPrefix)
    const imgUrlObj = new URL(imgUrl)
    if (imgUrlObj.pathname.startsWith(urlPrefixObj.pathname)) {
      fileKey = imgUrlObj.pathname.substring(urlPrefixObj.pathname.length).replace(/^\/+/, '')
    } else {
      fileKey = imgUrlObj.pathname.replace(/^\/+/, '')
    }
  } else {
    const url = new URL(!/^https?:\/\//.test(imgUrl) ? `http://${imgUrl}` : imgUrl)
    fileKey = url.pathname.replace(/^\/+/, '')
    if (pathStyleAccess) {
      fileKey = fileKey.replace(/^[^/]+\//, '')
    }
  }
  const endpointUrl: string | undefined = endpoint
    ? /^https?:\/\//.test(endpoint)
      ? endpoint
      : `http://${endpoint}`
    : undefined
  if (endpointUrl && endpointUrl.includes('cloudflarestorage')) {
    region = region || 'auto'
  }
  const sslEnabled = endpointUrl ? endpointUrl.startsWith('https') : true
  const agent = getAgent(proxy, sslEnabled)
  const commonOptions: AgentOptions = {
    keepAlive: true,
    keepAliveMsecs: 1000,
    scheduling: 'lifo' as 'lifo' | 'fifo' | undefined
  }
  const extraOptions = sslEnabled ? { rejectUnauthorized: !!rejectUnauthorized } : {}
  const handler = sslEnabled
    ? new NodeHttpHandler({
        httpsAgent: agent.https
          ? agent.https
          : new https.Agent({
              ...commonOptions,
              ...extraOptions
            })
      })
    : new NodeHttpHandler({
        httpAgent: agent.http
          ? agent.http
          : new http.Agent({
              ...commonOptions,
              ...extraOptions
            })
      })
  const s3Options: S3ClientConfig = {
    credentials: {
      accessKeyId: accessKeyID,
      secretAccessKey
    },
    endpoint: endpointUrl,
    tls: sslEnabled,
    forcePathStyle: pathStyleAccess,
    region,
    requestHandler: handler
  }
  if (dogeMode) {
    s3Options.credentials = {
      accessKeyId: configMap.config.accessKeyID,
      secretAccessKey: configMap.config.secretAccessKey,
      sessionToken: configMap.config.sessionToken
    }
  }
  let result: any
  try {
    fileKey = decodeURIComponent(fileKey)
  } catch (err: any) {}
  try {
    const client = new S3Client(s3Options)
    const command = new DeleteObjectCommand({
      Bucket: bucketName,
      Key: fileKey
    })
    result = await client.send(command)
  } catch (err: any) {
    s3Options.region = 'us-east-1'
    const client = new S3Client(s3Options)
    const command = new DeleteObjectCommand({
      Bucket: bucketName,
      Key: fileKey
    })
    result = await client.send(command)
  }
  return result.$metadata.httpStatusCode === 204
}

export async function removeFileFromDogeInMain(configMap: IStringKeyMap) {
  const {
    config: { bucketName, AccessKey, SecretKey }
  } = configMap
  const token = (await getDogeToken(AccessKey, SecretKey)) as DogecloudTokenFull
  const bucket = token.Buckets?.find(item => item.name === bucketName || item.s3Bucket === bucketName)
  const newConfigMap = { ...configMap }
  newConfigMap.config = {
    ...newConfigMap.config,
    accessKeyID: token.Credentials?.accessKeyId,
    secretAccessKey: token.Credentials?.secretAccessKey,
    sessionToken: token.Credentials?.sessionToken,
    endpoint: bucket?.s3Endpoint,
    region: dogeRegionMap[bucket?.s3Endpoint?.split('.')[1] || 'ap-shanghai'],
    bucketName: bucket?.s3Bucket
  }
  return await removeFileFromS3InMain(newConfigMap, true)
}

function createHuaweiAuthorization(
  bucketName: string,
  path: string,
  fileName: string,
  accessKey: string,
  secretKey: string,
  date: string = new Date().toUTCString()
) {
  const strToSign = `DELETE\n\n\n${date}\n/${bucketName}${path}/${fileName}`
  const singature = crypto.createHmac('sha1', secretKey).update(strToSign).digest('base64')
  return `OBS ${accessKey}:${singature}`
}

export async function removeFileFromHuaweiInMain(configMap: IStringKeyMap) {
  const { fileName, config } = configMap
  const { accessKeyId, accessKeySecret, bucketName, endpoint } = config
  let path = config.path || '/'
  path = `/${path.replace(/^\/+|\/+$/, '')}`
  path = path === '/' ? '' : path
  const date = new Date().toUTCString()
  const authorization = createHuaweiAuthorization(bucketName, path, fileName, accessKeyId, accessKeySecret, date)
  const res = await axios.request({
    url: `https://${bucketName}.${endpoint}${encodeURI(path)}/${encodeURIComponent(fileName)}`,
    method: 'DELETE',
    responseType: 'json',
    headers: {
      Host: `${bucketName}.${endpoint}`,
      Date: date,
      Authorization: authorization
    }
  })
  return res.status === 204
}

export async function removeFileFromSFTPInMain(config: ISftpPlistConfig, fileName: string) {
  const client = SSHClient.instance
  await client.connect(config)
  const uploadPath = `/${config.uploadPath || ''}/`.replace(/\/+/g, '/')
  const remote = path.join(uploadPath, fileName)
  const deleteResult = await client.deleteFileSFTP(config, remote)
  client.close()
  return deleteResult
}
